/**
 * Framework for batch testing user profile models
 * Final project by Sergey Nepomnyachiy and Julia Polchin
 * Supervisor: Tsvi Kuflik
 *
 */
package models.collaborative;

import java.util.Collections;
import java.util.Vector;

/**
 * @author Sergey and Julia
 * 
 */
public class KNN {
	private Vector<Vector<Double>> neighbourhood;
	private int k;
	/**
	 * Simple tuple class for distance-name pairs 
	 */
	private class DistanceName implements java.lang.Comparable<DistanceName> {
		/**
		 * index of user in user*item matrix
		 */
		public Integer userIndex;
		/**
		 * distance of this user to new user
		 */
		public Double distance;

		/**
		 * DistanceName constructor 
		 * @param d - distance
		 * @param c - index
		 */
		public DistanceName(Double d, Integer c) {
			this.userIndex = c;
			this.distance = d;
		}

		@SuppressWarnings("boxing")
		@Override
		public int compareTo(DistanceName arg0) {
			Double a1 = arg0.distance;
			if (this.distance <= a1)
				return -1;
			if (this.distance > a1)
				return 1;
			return 0;
		}
	}

	/**
	 * @param matrix
	 * @param k
	 */
	public KNN(Vector<Vector<Double>> matrix, int k) {
		setNewNeighbourhood(matrix);
		this.k = k;
	}

	/**
	 * @param matrix
	 */
	public void setNewNeighbourhood(Vector<Vector<Double>> matrix) {
		this.neighbourhood = matrix;
	}

	/**
	 * @param k
	 */
	public void setK(int k) {
		this.k = k;
	}

	/**
	 * @param newVec
	 * @return the nearest vector - being average of k nearest
	 */
	@SuppressWarnings("boxing")
	public Vector<Double> getNearestOne(Vector<Double> newVec) {
		Vector<Double> result = new Vector<Double>();
		int neighSize = this.neighbourhood.size();
		Vector<DistanceName> distances = new Vector<DistanceName>(neighSize);

		for (int i = 0; i < neighSize; ++i)
			distances.add(new DistanceName(Formulae.cosine(this.neighbourhood
					.elementAt(i), newVec), i));

		Collections.sort(distances); // all neighbourhood is sorted from nearest
		// to farest

		int count = Math.min(this.k, distances.size());
		for (int n = 0; n < getItemsCount(); ++n) {
			Double acc = 0.0;
			for (int i = 0; i < count; ++i) {
				int index = distances.elementAt(i).userIndex;
				Vector<Double> singleUser = this.neighbourhood.elementAt(index);
				acc += singleUser.elementAt(n);
			}
			result.add(acc / (0.0 + count));
		}
		return result;
	}

	/**
	 * @param ratings
	 */
	public void addNeighbour(Vector<Double> ratings) {
		this.neighbourhood.add(ratings);
	}

	/**
	 * 
	 */
	public void clearAll() {
		this.neighbourhood.clear();
	}

	/**
	 * @return
	 */
	public int getItemsCount() {
		return this.neighbourhood.elementAt(0).size();
	}

}
